import json
import re
import time

import requests
import urllib3
from bs4 import BeautifulSoup

from Problems.models import Problem
from Problems.serializers import ProblemDetailSerializer
from Robots.BaseRobot import BaseRobot
from Robots.Exceptions import AuthFailed, SubmitProblemFailed
from Robots.models import RemoteOJ
from Utils.GetCFContest import get_cf_contest

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


def calcTta(_39ce7):
    n = len(_39ce7)
    c = 0
    _tta = 0
    while c < n:
        _tta = (_tta + (c + 1) * (c + 2) * ord(_39ce7[c])) % 1009
        if c % 3 == 0:
            _tta += 1
        if c % 2 == 0:
            _tta *= 2
        if c > 0:
            _tta -= ((ord(_39ce7[c // 2]) // 2)) * (_tta % 5)
        while _tta < 0:
            _tta += 1009
        while _tta >= 1009:
            _tta -= 1009
        c += 1
    return str(_tta)


class CodeForcesRobot(BaseRobot):
    def __init__(self, cookies=None, token="", username=None, password=None):
        super().__init__(cookies, token, username, password)
        self.session = None

    def save(self):
        return {"cookies": self.cookies, "token": self.token}

    def check_url(self, url):
        return re.compile(r"^https://codeforces.com/problemset/problem/\d+/[A-Z]$").match(url) is not None

    def _get_token(self):
        pass

    def login(self, username, password):
        self._get_token()
        r = self.post("https://codeforces.com/enter",
                      data={"csrf_token": self.token, "action": "enter", "handleOrEmail": username,
                            "password": password, "remember": "on"},
                      cookies=self.cookies)
        if r.status_code != 302:
            raise AuthFailed("Failed to login CodeForces")
        self.cookies.update(dict(r.cookies))
        self._get_token()

    @property
    def is_logged_in(self):
        r = self.get("https://codeforces.com/settings/general", cookies=self.cookies)
        return r.status_code == 200

    def get_problem(self, problem_id):
        uid = problem_id[2:]
        id = 0
        for ch in uid:
            if not ch.isdigit():
                break
            id += 1

        index = uid[id:]
        flg = 0
        try:
            problem = Problem.objects.get(remote_id=uid, remote_oj__name="CF")
        except Problem.DoesNotExist:
            problem = None
        else:
            flg = 1

        if problem is not None and problem.content is not None:
            return ProblemDetailSerializer(problem).data

        url = f"https://codeforces.com/problemset/problem/{uid[:id]}/{index}"
        r = self.get(url, headers={"Referer": "https://codeforces.com",
                                   "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0"})
        regex = {"title": r'<div class="header"><div class="title">([\s\S]*?)</div>',
                 "time_limit": r'<div class="time-limit"><div class="property-title">time limit per test</div>(\d+)\s*second',
                 "memory_limit": r'<div class="memory-limit"><div class="property-title">memory limit per test</div>(\d+)\s*megabytes</div>',
                 "description": r'<div class="output-file"><div class="property-title">output</div>[\s\S]*?</div></div><div>([\s\S]*?)</div>',
                 "input_description": r'<div class="section-title">Input</div>([\s\S]*?)</div>',
                 "hint": r'<div class="note"><div class="section-title">Note</div>([\s\S]*?)</div>',
                 "output_description": r'<div class="section-title">Output</div>([\s\S]*?)</div>'}
        input_samples_regex = r'<div class="title">Input</div><pre>([\s\S]*?)</pre></div>'
        output_samples_regex = r'<div class="title">Output</div><pre>([\s\S]*?)</pre></div>'
        data = {}
        for k, v in regex.items():
            items = re.compile(v).findall(r.text)
            if not items:
                if k == "hint":
                    data[k] = None
                    continue
                raise Problem.DoesNotExist("No such problem")
            data[k] = self._clean_html(items[0])

        data["is_spj"] = False
        data["remote_oj"] = RemoteOJ.objects.get(name="CF")

        data["remote_id"] = uid
        data["samples"] = []
        data["memory_limit"] = data["memory_limit"] + "MB"
        data["time_limit"] = data["time_limit"] + "s"
        input_samples = re.compile(input_samples_regex).findall(r.text)
        output_samples = re.compile(output_samples_regex).findall(r.text)
        for i in range(len(input_samples)):
            data["samples"].append(
                {"input": self._clean_html(input_samples[i]), "output": self._clean_html(output_samples[i])})

        data_content = {}
        data_content["samples"] = data.pop("samples")
        data_content["input_description"] = data.pop("input_description")
        data_content["output_description"] = data.pop("output_description")
        data_content["hint"] = data.pop("hint")
        data_content["description"] = data.pop("description")

        data["content"] = data_content

        problem = Problem.objects.create(**data)

        # if flg == 0:  # 数据库中没有，直接保存
        #     serializer = ProblemDetailSerializer(data=data)
        #     if serializer.is_valid():
        #         serializer.save()
        #     else:
        #         print(serializer.errors)
        # else:
        #     # 数据库中存在，需要更新,将 problem 更新为 problem 2
        #     serializer = ProblemDetailSerializer(problem, data=data)
        #     if serializer.is_valid():
        #         serializer.save()

        return ProblemDetailSerializer(problem).data

    def submit(self, language, code, origin_id):
        submit_url = "https://codeforces.com/problemset/submit"
        if "C" in language or "c" in language:
            language = "54"

        session = requests.Session()
        url = 'https://codeforces.com/enter'
        r = session.get(url, verify=False)
        soup = BeautifulSoup(r.text, features="html.parser")
        _csrf = soup.find(attrs={"name": "X-Csrf-Token"})['content']
        _39ce7 = requests.utils.dict_from_cookiejar(r.cookies)["39ce7"]
        _tta = calcTta(_39ce7)
        self.token = _csrf
        formData = {
            'csrf_token': _csrf,
            '_tta': _tta,
            'action': 'enter',
            'handleOrEmail': self.username,
            'password': self.password,
            'remember': 'on'
        }
        r = session.post(url, formData, verify=False)
        url = 'https://codeforces.com/problemset/submit'
        r = session.get(url, verify=False)
        soup = BeautifulSoup(r.text, "html.parser")
        _csrf = soup.find(attrs={"name": "X-Csrf-Token"})['content']
        formData = {
            'csrf_token': _csrf,
            # '_tta': _tta,
            'action': 'submitSolutionFormSubmitted',
            "submittedProblemCode": origin_id,
            "programTypeId": language,  # 54 for GNU G++17 7.3.0
            "source": code,
            "sourceFile": "",
            "sourceCodeConfirmed": "true",
            "doNotShowWarningAgain": "on"
        }
        url += '?csrf_token=' + _csrf

        r = session.post(url, formData, verify=False)

        self.session = session

        if re.compile(r"You have submitted exactly the same code before").search(r.text) is not None:
            self.release()
            raise SubmitProblemFailed("You have submitted exactly the same code before")

        if re.compile(r"Source should satisfy regex").search(r.text) is not None:
            self.release()
            raise SubmitProblemFailed("Source should satisfy regex [^{}]*public\s+(final)?\s*class\s+(\w+).*")

        if re.compile(r"\<title\>Codeforces\</title\>").search(r.text) is not None:
            self.release()
            raise SubmitProblemFailed("Failed to submit problem, url: %s code %d" % (submit_url, r.status_code))

        url = f"https://codeforces.com/api/user.status?handle={self.username}&from=1&count=1"

        time.sleep(6)

        last = None
        while True:
            ret = session.get(url, verify=False)
            if ret.status_code != 200:
                continue
            ret = json.loads(ret.text)["result"][0]["id"]
            if ret != last:
                last = ret
            else:
                break
            time.sleep(2)
        return last

    def get_result(self, submission_id, username=None, contestId=None):
        url = f"https://codeforces.com/contest/{get_cf_contest(contestId)}/submission/{submission_id}"
        r = requests.get(url, headers={
            "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
                          "Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0"})

        soup = BeautifulSoup(r.text, features="html.parser")
        table = soup.find("div", id="pageContent").find("div", class_="datatable").find_all("td")

        data = {}
        data["remote_verdict"] = table[4].text.strip()
        data["time_cost"] = table[5].text.strip()
        data["memory_cost"] = table[6].text.strip()

        return data

    def __str__(self):
        if self.username:
            return f"CF机器人: {self.username}"
        return "CF匿名机器人"

    def release(self):
        if self.username is not None:
            # 找到 username 对应的账户并把 is_active 字段修改为 true,并保存
            from Robots.models.RobotAccount import RobotAccount
            RobotAccount.objects.filter(username=self.username, password=self.password,
                                        remote_oj__name="CF").update(is_active=True)
